import * as THREE from '../../build/three.module.js';
import { GUI } from '../../jsm/libs/dat.gui.module.js'
import { OrbitControls } from '../../jsm/controls/OrbitControls.js'
import { OBJLoader } from '../../jsm/loaders/OBJLoader.js'

let bufferGeo, mesh, curvatureAttribute;

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 1000);
camera.position.z = 24;

const renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(devicePixelRatio);
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

new OrbitControls(camera, renderer.domElement);

const render = () => {
  renderer.render(scene, camera);
}

const animate = () => {
  requestAnimationFrame(animate);
  render();
}

// 工具函数
const average = dict => {
  let sum = 0, l = 0;
  Object.keys(dict).forEach(key => {
    sum += dict[key];
    l++;
  })
  return sum / l;
}
const clamp = (number, min, max) => {
  return Math.max( min, Math.min(number, max) );
}
// 过滤出凹面曲率数据
const filterConcave = curvature => {
  for(let i = 0; i < curvature.length; i += 1) {
    curvature[i] = Math.abs( clamp(curvature[i], -1, 0) )
  }
}
// 过滤出凸面曲率数据
const filterConvex = curvature => {
  for(let i = 0; i < curvature.length; i += 1) {
    curvature[i] = Math.abs( clamp(curvature[i], 0, 1) )
  }
}
// 凹面、凸面均显示
const filterBoth = curvature => {
  for(let i = 0; i < curvature.length; i += 1) {
    curvature[i] = Math.abs( curvature[i] )
  }
}

// 加载模型
const loader = new OBJLoader();
loader.load('../../models/obj/ninja/ninjaHead_Low.obj', object => {
  object.traverse(child => {

    if (child.isMesh) {

      bufferGeo = child.geometry;
      bufferGeo.center();
      const dict = {};

      for(let i = 0; i < bufferGeo.attributes.position.count; i += 3) {

        const array = bufferGeo.attributes.position.array; // 顶点坐标列表
        const normalArray = bufferGeo.attributes.normal.array; // 法线坐标列表

        // 构成一个基础三角形的相邻的三个顶点
        const posA = new THREE.Vector3( array[3 * i], array[3 * i + 1], array[3 * i + 2] );
        const posB = new THREE.Vector3( array[(i + 1) * 3], array[(i + 1) * 3 + 1], array[(i + 1) * 3 + 2] );
        const posC = new THREE.Vector3( array[(i + 2) * 3], array[(i + 2) * 3 + 1], array[(i + 2) * 3 + 2] );

        // 上面每个顶点处的法线向量
        const normA = new THREE.Vector3( normalArray[3 * i], normalArray[3 * i + 1], normalArray[3 * i + 2] ).normalize();
        const normB = new THREE.Vector3( normalArray[3 * (i + 1)], normalArray[3 * (i + 1) + 1], normalArray[3 * (i + 1) + 2] ).normalize();
        const normC = new THREE.Vector3( normalArray[3 * (i + 2)], normalArray[3 * (i + 2) + 1], normalArray[3 * (i + 2) + 2] ).normalize();

        // 将顶点的位置向量坐标字符串化，作为集合的键。
        const strA = posA.toArray().toString();
        const strB = posB.toArray().toString();
        const strC = posC.toArray().toString();

        // 三个顶点所围成三角形的边向量
        const posB_A = new THREE.Vector3().subVectors(posB, posA);
        const posB_C = new THREE.Vector3().subVectors(posB, posC);
        const posC_A = new THREE.Vector3().subVectors(posC, posA);

        // 顶点法向量与相应边向量的点积
        const b2a = normB.dot( posB_A.normalize() );
        const b2c = normB.dot( posB_C.normalize() );
        const c2a = normC.dot( posC_A.normalize() );

        // 顶点法向量与相应边向量的点积
        const a2b = - normA.dot( posB_A.normalize() );
        const c2b = - normC.dot( posB_C.normalize() );
        const a2c = - normA.dot( posC_A.normalize() );

        // 初始化数据容器，每个键表示一个顶点
        if (dict[strA] === undefined) {
          dict[strA] = {};
        }
        if (dict[strB] === undefined) {
          dict[strB] = {};
        }
        if (dict[strC] === undefined) {
          dict[strC] = {};
        }

        // 记录对应顶点的法向量与从该顶点起的两条边的点积
        dict[strA][strB] = a2b;
        dict[strA][strC] = a2c;
        dict[strB][strA] = b2a;
        dict[strB][strC] = b2c;
        dict[strC][strA] = c2a;
        dict[strC][strB] = c2b;

      }

      let curvatrueDict = {};
      let min = 10, max = 0;

      // 每个顶点下点积平均值
      Object.keys( dict ).forEach(key => {
        curvatrueDict[key] = average(dict[key]);
      })

      // 平滑处理
      const smoothCurvatureDict = Object.create( curvatrueDict );
      
      Object.keys(dict).forEach(key => {
        let sum = 0, count = 0;
        Object.keys(dict[key]).forEach(key2 => {
          sum += smoothCurvatureDict[key2];
          count++;
        })
        smoothCurvatureDict[key] = sum / count;
      })

      curvatrueDict = smoothCurvatureDict;

      // 找到点积的最大最小值，计算范围，并将点积数值化为 0-1 范围内
      Object.keys(curvatrueDict).forEach(key => {
        const val = Math.abs( curvatrueDict[key] );
        if (val < min) min = val;
        if (val > max) max = val;
      })

      const range = max - min;

      Object.keys(curvatrueDict).forEach(key => {
        const val = Math.abs( curvatrueDict[key] );

        if (curvatrueDict[key] < 0) {
          curvatrueDict[key] = (min - val) / range;
        } else {
          curvatrueDict[key] = (val - min) / range;
        }
      })

      // 构建几何体属性
      curvatureAttribute = new Float32Array( bufferGeo.attributes.position.count );

      for(let i = 0; i < bufferGeo.attributes.position.count; i += 1) {
        const array = bufferGeo.attributes.position.array;
        const pos = new THREE.Vector3( array[3 * i], array[3 * i + 1], array[3 * i + 2] );
        const str = pos.toArray().toString();
        curvatureAttribute[i] = curvatrueDict[str];
      }

      bufferGeo.setAttribute('curvature', new THREE.BufferAttribute(curvatureAttribute, 1));

      // 过滤要显示的凹面和凸面
      const curvatureFiltered = new Float32Array(curvatureAttribute);
      filterBoth(curvatureFiltered);

      // 材质
      const material = new THREE.ShaderMaterial({
        vertexShader: document.getElementById('vertexShaderRaw').textContent,
        fragmentShader: document.getElementById('fragmentShaderRaw').textContent
      });

      // 网格模型
      mesh = new THREE.Mesh(bufferGeo, material);

    }

  })

  scene.add(mesh);

});

animate();

// 初始化 GUI
var params = {
  filterConvex: function () {
    var curvatureFiltered = new Float32Array( curvatureAttribute );
    filterConvex( curvatureFiltered );
    bufferGeo.attributes.curvature.array = curvatureFiltered;
    bufferGeo.attributes.curvature.needsUpdate = true;
  },
  filterConcave: function () {
    var curvatureFiltered = new Float32Array( curvatureAttribute );
    filterConcave( curvatureFiltered );
    bufferGeo.attributes.curvature.array = curvatureFiltered;
    bufferGeo.attributes.curvature.needsUpdate = true;
  },
  filterBoth: function () {
    var curvatureFiltered = new Float32Array( curvatureAttribute );
    filterBoth( curvatureFiltered );
    bufferGeo.attributes.curvature.array = curvatureFiltered;
    bufferGeo.attributes.curvature.needsUpdate = true;
  }
};

var gui = new GUI();

var topologyFolder = gui.addFolder( 'Topology' );
topologyFolder.add( params, 'filterConvex' );
topologyFolder.add( params, 'filterConcave' );
topologyFolder.add( params, 'filterBoth' );
topologyFolder.open();

const onWindowResize = () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(innerWidth, innerHeight);
}
window.addEventListener('resize', onWindowResize, false);